﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace OpenCompiler.CodeGen.Implementation
{
	public class MethodBuilder : IMethodBuilder
	{
		public string Name { get; protected set; }
		public Visibility Visibility { get; protected set; }
		public ITypeBuilder TypeBuilder { get; protected set; }

		protected Dictionary<MethodBodyContext, IMethodBodyBuilder> _bodyBuilders;


		internal MethodBuilder(ITypeBuilder typeBuilder)
		{
			this.TypeBuilder = typeBuilder;
			this._bodyBuilders = new Dictionary<MethodBodyContext, IMethodBodyBuilder>();
		}

		public IMethodBuilder WithName(string name)
		{
			this.Name = name;
			return this;
		}

		public IMethodBuilder VisibleTo(Visibility visibility)
		{
			this.Visibility = visibility;
			return this;
		}

		protected IMethodBodyBuilder GetBody(MethodBodyContext context)
		{
			if( this._bodyBuilders.ContainsKey(context) )
			{
				return this._bodyBuilders[context];
			}
			var bodyBuilder = this.CreateBodyBuilder(context);
			this._bodyBuilders[context] = bodyBuilder;
			return bodyBuilder;
		}

		protected virtual IMethodBodyBuilder CreateBodyBuilder(MethodBodyContext context)
		{
			var bodyBuilder = new MethodBodyBuilder(this, context);
			return bodyBuilder;
		}


		public IMethodBodyBuilder Body(MethodBodyContext context)
		{
			var bodyBuilder = this.GetBody(context);
			
			return bodyBuilder;
		}

		
		private IMethodBodyBuilder GetBodyBuilderForContext(MethodBodyContext context)
		{
			if( this._bodyBuilders.ContainsKey(context))
			{
				return this._bodyBuilders[context];
			}
			return null;
		}


		public Expression ComposedBody
		{
			get 
			{
				var preBodyBuilder = this.GetBodyBuilderForContext(MethodBodyContext.Pre);
				var mainBodyBuilder = this.GetBodyBuilderForContext(MethodBodyContext.Body);
				var postBodyBuilder = this.GetBodyBuilderForContext(MethodBodyContext.Post);

				Expression body = null;
				if( null != preBodyBuilder && null != preBodyBuilder.Body )
				{
					body = preBodyBuilder.Body;
				}
				if( null != mainBodyBuilder && null != mainBodyBuilder.BodyWithoutReturnStatement )
				{
					body = body.Merge(mainBodyBuilder.Body);
				}
				if( null != postBodyBuilder && null != postBodyBuilder.Body)
				{
					body = body.Merge(postBodyBuilder.Body);
				}
				if( null != mainBodyBuilder && null != mainBodyBuilder.ReturnStatementBody)
				{
					body = body.Merge(mainBodyBuilder.ReturnStatementBody);
				}

				return body;
			}
		}
	}

	public class MethodBuilder<T> : MethodBuilder, IMethodBuilder<T>
		where T:class
	{
		internal MethodBuilder(ITypeBuilder typeBuilder)
			: base(typeBuilder)
		{
			
		}


		public new IMethodBuilder<T> WithName(string name)
		{
			this.Name = name;
			return this;
		}

		public new IMethodBuilder<T> VisibleTo(Visibility visibility)
		{
			this.Visibility = visibility;
			return this;
		}


		protected override IMethodBodyBuilder CreateBodyBuilder(MethodBodyContext context)
		{
			var bodyBuilder = new MethodBodyBuilder<T>(this, context);
			return bodyBuilder;
		}

		public new IMethodBodyBuilder<T> Body(MethodBodyContext context)
		{
			var bodyBuilder = this.GetBody(context);
			return bodyBuilder as IMethodBodyBuilder<T>;
		}
	}

	public class MethodBuilder<T,E> : MethodBuilder, IMethodBuilder<T,E>
		where T: class 
		where E: Expression
	{
		internal MethodBuilder(ITypeBuilder typeBuilder)
			: base(typeBuilder)
		{
			
		}

		public new IMethodBuilder<T, E> WithName(string name)
		{
			this.Name = name;
			return this;
		}

		public new IMethodBuilder<T, E> VisibleTo(Visibility visibility)
		{
			this.Visibility = visibility;
			return this;
		}

		protected override IMethodBodyBuilder CreateBodyBuilder(MethodBodyContext context)
		{
			var bodyBuilder = new MethodBodyBuilder<T, E>(this, context);
			return bodyBuilder;
		}

		public new IMethodBodyBuilder<T, E> Body(MethodBodyContext context)
		{
			var bodyBuilder = this.GetBody(context);
			return bodyBuilder as IMethodBodyBuilder<T,E>;
		}
	}


}
